import os
from re import split as re_split
from PySide2.QtCore import QThread, Qt
from PySide2.QtWidgets import (
	QWidget, QTableWidgetItem, QScrollArea, QComboBox, QSpinBox, QFrame,
	QLabel, QVBoxLayout, QHBoxLayout, QFrame, QCheckBox, QMenu, QAction
)
from PySide2.QtGui import QCursor

class InvalidItem(QTableWidgetItem):
	def __init__(self, val):
		super(InvalidItem, self).__init__()
		self.setText(val)
		self.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)

class DefaultSpinBox(QSpinBox):
	def __init__(self, val, width=55):
		super(DefaultSpinBox, self).__init__()
		self.setMinimum(1)
		self.setMaximum(2147483647)
		self.setValue(val)
		self.setMaximumWidth(width)
		self.setMinimumWidth(width)

class DefaultComboBox(QComboBox):
	def __init__(self, val, width=410):
		super(DefaultComboBox, self).__init__()
		self.addItems(val)
		self.setMaximumWidth(width)
		self.setMinimumWidth(width)

class DefaultLabel(QLabel):
	def __init__(self, val, width=90):
		super(DefaultLabel, self).__init__(val)
		self.setMaximumWidth(width)
		self.setMinimumWidth(width)

class DefaultCheckBox(QCheckBox):
	def __init__(self, name):
		super(DefaultCheckBox, self).__init__(name)
		self.setChecked(False)

class DataLine(QFrame):
	def __init__(self, widgets):
		super(DataLine, self).__init__()
		self.hlayout = QHBoxLayout(self)
		for w in widgets:
			self.hlayout.addWidget(w)
		self.setLayout(self.hlayout)
		self.hlayout.addStretch()

class TransferAddrData(QScrollArea):
	def __init__(self):
		super(TransferAddrData, self).__init__()
		self.content = QWidget(self)
		self.vlayout = QVBoxLayout(self.content)
		
		self.label_input = DefaultLabel("输入列：", width=50)
		self.sp_input = DefaultSpinBox(2)
		
		self.label_output = DefaultLabel("输出列：", width=50)
		self.sp_output = DefaultSpinBox(2)

		self.label_type = DefaultLabel("地址类型：")
		self.cb_type = DefaultComboBox([
			"SCOPUS一般地址", 
			"SCOPUS通讯地址", 
			"WOS一般地址", 
			"WOS通讯地址",
		])
		
		self.check_bind_author = DefaultCheckBox("绑定作者")

		self.vlayout.addWidget(DataLine([self.label_input, self.sp_input, self.label_output, self.sp_output, self.check_bind_author]))
		self.vlayout.addWidget(DataLine([self.label_type, self.cb_type]))
		
		self.setMaximumHeight(180)
		self.setWidget(self.content)
		self.setWidgetResizable(True)

		self.setFrameShape(QFrame.NoFrame)

class CountAddrData(QScrollArea):
	def __init__(self):
		super(CountAddrData, self).__init__()
		self.content = QWidget(self)
		self.vlayout = QVBoxLayout(self.content)
		
		self.label_input = DefaultLabel("输入列：", width=50)
		self.sp_input = DefaultSpinBox(2)
		
		self.check_bind_author = DefaultCheckBox("绑定作者")

		self.label_algo = DefaultLabel("贡献分配算法：")
		self.cb_algo = DefaultComboBox([
			"均分",
			"全分（每个出现的学院都分得整个份额）",
			"一作占1/2，余下1/2按2(n-k+1)/(n+1)(n-2)计算权重分配",
		])

		self.vlayout.addWidget(DataLine([self.label_input, self.sp_input, self.check_bind_author]))
		self.vlayout.addWidget(DataLine([self.label_algo, self.cb_algo]))
		self.setMaximumHeight(180)
		self.setWidget(self.content)
		self.setWidgetResizable(True)

		self.setFrameShape(QFrame.NoFrame)

class TableMenu:
	def __init__(self, table):
	    table.setContextMenuPolicy(Qt.CustomContextMenu)
	    table.customContextMenuRequested.connect(self.on_table_right_click)
	    self.table = table
	    self.items = {}

	def on_table_right_click(self):
		self.menu = QMenu(self.table)
		for item in self.items:
			self.menu.addAction(item, self.items[item])

		self.menu.exec_(QCursor.pos())

	def on_menu_triggered(self, act: QAction):
		for item in self.items:
			if item == act.text():
				self.items[item]()
				return

	def del_selected_row(self):
	    table = self.table
	    selected_items = table.selectedItems()
	    selected_items = list(set([i.row() for i in selected_items]))
	    selected_items.sort(reverse=True)
	    for i in selected_items:
	        table.removeRow(i)

class AddrTableMenu(TableMenu):
	def __init__(self, table):
	    super(AddrTableMenu, self).__init__(table)

	    self.items = {
	    	"添加转换地址": self.add_transfer_addr,
	    	"添加统计地址": self.add_count_addr,
	    	"删除选中": self.del_selected_row,
	    }

	def add_transfer_addr(self):
	    table = self.table
	    row = table.rowCount()
	    table.insertRow(row)
	    table.setItem(row, 0, QTableWidgetItem(""))
	    table.setItem(row, 1, InvalidItem("转换地址"))
	    table.setCellWidget(row, 2, TransferAddrData())
	    table.setRowHeight(row, 180)

	def add_count_addr(self):
	    table = self.table
	    row = table.rowCount()
	    table.insertRow(row)
	    table.setItem(row, 0, QTableWidgetItem(""))
	    table.setItem(row, 1, InvalidItem("统计地址"))
	    table.setCellWidget(row, 2, CountAddrData())
	    table.setRowHeight(row, 180)

class StatisticTableMenu(TableMenu):
	def __init__(self, table):
	    super(StatisticTableMenu, self).__init__(table)

	    self.items = {
	    	"添加统计指标": self.add_count_item,
	    	"删除选中": self.del_selected_row,
	    }

	def add_count_item(self):
	    table = self.table
	    row = table.rowCount()
	    table.insertRow(row)
	    table.setItem(row, 0, QTableWidgetItem(""))
	    sp = DefaultSpinBox(2)
	    sp.setMaximumWidth(25000)
	    table.setCellWidget(row, 1, sp)

class UniqueNameAllocator:
    def __init__(self):
        self.name_set = set()

    def get_unique_name(self, origin):
        while origin in self.name_set:
            origin = f"{origin}_"
        self.name_set.add(origin)
        return origin

def file_path_add_sub(file_path, sub_str):
	dir_path, file_name = os.path.split(file_path)
	file_name, file_ext = os.path.splitext(file_name)
	return dir_path+"/"+file_name+sub_str+file_ext

# 收集表格数据，返回二维列表，第一维表示各行，第二维为各数据，包括输入列、输出列、地址类型、是否绑定作者
def collect_transfer_addr(table):
	unique_set = UniqueNameAllocator()
	res = []
	row = table.rowCount()
	col = table.columnCount()
	cnt = 0
	for i in range(0, row):
		info_type = table.item(i, 1)

		if info_type.text() != "转换地址":
			continue

		tmp = []
		name = table.item(i, 0)
		
		if name.text() == "":
			tmp.append(unique_set.get_unique_name(f"{i+1}"))
		else:
			tmp.append(unique_set.get_unique_name(name.text()))

		transfer_data = table.cellWidget(i, 2)

		tmp.append(transfer_data.sp_input.value())
		tmp.append(transfer_data.sp_output.value())
		tmp.append(transfer_data.cb_type.currentText())
		tmp.append(transfer_data.check_bind_author.isChecked())

		res.append(tmp)
	return res

# 收集表格数据，返回二维列表，第一维表示各行，第二维为各数据，包括输入列、地址类型、是否绑定作者、算法类型
def collect_count_addr(table):
	unique_set = UniqueNameAllocator()
	res = []
	row = table.rowCount()
	col = table.columnCount()
	cnt = 0
	for i in range(0, row):
		info_type = table.item(i, 1)

		if info_type.text() != "统计地址":
			continue

		tmp = []
		name = table.item(i, 0)
		
		if name.text() == "":
			tmp.append(unique_set.get_unique_name(f"{i+1}"))
		else:
			tmp.append(unique_set.get_unique_name(name.text()))

		count_data = table.cellWidget(i, 2)

		tmp.append(count_data.sp_input.value())
		tmp.append(count_data.check_bind_author.isChecked())
		tmp.append(count_data.cb_algo.currentIndex())

		res.append(tmp)
	return res

# 收集表格数据，返回二维列表，第一维表示各行，第二维为数据，包括输入列
def collect_statistic_item(table):
	unique_set = UniqueNameAllocator()
	res = []
	row = table.rowCount()
	col = table.columnCount()
	cnt = 0
	for i in range(0, row):
		cnt += 1
		tmp = []
		first_item = table.item(i, 0)
		
		if first_item.text() == "":
			tmp.append(unique_set.get_unique_name(f"{cnt}"))
		else:
			tmp.append(unique_set.get_unique_name(first_item.text()))

		tmp.append(table.cellWidget(i, 1).value())

		res.append(tmp)
	return res

class FunctionalThread(QThread):
    def __init__(self, func, *args):
        super(FunctionalThread, self).__init__()
        self.func = func
        self.args = args

    def run(self):
        self.func(self.args)

class WeightAllocator:
	def __init__(self):
		self.cache = {}

	def gen_weights(self, number):
		return []

	def get_weights(self, number):
		if number in self.cache:
			return self.cache[number]

		weights = self.gen_weights(number)
		self.cache[number] = weights
		return weights

class EqualWeightAllocator(WeightAllocator):
	def __init__(self):
		super(EqualWeightAllocator, self).__init__()

	def gen_weights(self, number):
		if number == 0:
			return []
		if number == 1:
			return [1]
		if number == 2:
			return [1/2, 1/2]

		return [1/number] * number

class AllWeightAllocator(WeightAllocator):
	def __init__(self):
		super(AllWeightAllocator, self).__init__()

	def gen_weights(self, number):
		return [1] * number

class ExprWeightAllocator(WeightAllocator):
	def __init__(self, expr):
		super(ExprWeightAllocator, self).__init__()
		self.expr = expr

	def gen_weights(self, number):
		if number == 0:
			return []
		if number == 1:
			return [1]
		if number == 2:
			return [1/2, 1/2]
		if number == 3:
			return [1/3, 1/3, 1/3]

		n = number-1
		raw_weights = []
		for k in range(1, number):
			raw_weights.append(eval(self.expr))
		raw_sum = sum(raw_weights)
		divisor = raw_sum*2
		raw_weights = [i/divisor for i in raw_weights]
		
		res = [1/2]
		res.extend(raw_weights)

		return res

# SCOPUS一般地址分割函数，按";"切分
def addr_split_scopus_general(addr_str):
	if addr_str == None or addr_str == "":
		return []

	return addr_str.split(";")

# SCOPUS通讯地址分割函数，先按";"切分，然后根据包含"email"的块分组并重新拼接为各个地址
def addr_split_scopus_corr(addr_str):
	if addr_str == None or addr_str == "":
		return []

	res = []
	addr = addr_str.split(";")
	lf = 0
	for i in range(len(addr)):
		if "email" in addr[i]:
			res.append(";".join(addr[lf:i+1]))
			lf = i+1
		elif "外校" in addr[i]:
			res.append(addr[i])
			lf = i+1

	return res

# WOS一般地址分割函数，按"; ["切分
def addr_split_wos_general(addr_str):
	if addr_str == None or addr_str == "":
		return []

	return re_split(r"; \[", addr_str)

def addr_split_wos_corr(addr_str):
	if addr_str == None or addr_str == "":
		return []

	res = []
	addr = addr_str.split(";")
	lf = 0
	for i in range(len(addr)):
		if "corresponding author" in addr[i]:
			res.append(";".join(addr[lf:i+1]))
			lf = i+1
		elif "外校" in addr[i]:
			res.append(addr[i])
			lf = i+1

	return res

def extract_author_scopus_general(addr_str):
	if "↔" in addr_str:
		parts_join_str = "↔"
	else:
		parts_join_str = ","

	return [addr_str.split(parts_join_str)[0].strip()]

def extract_author_scopus_corr(addr_str):
	return [addr_str.split(";")[0].strip()]

def extract_author_wos_general(addr_str):
	# 地址中没有作者
	if not "]" in addr_str:
		return []
	addr_str = addr_str.split("]")[0]
	addr_str = addr_str.replace("[", "")
	author_list = addr_str.split(";")
	author_list = [i.strip() for i in author_list]
	return author_list

def extract_author_wos_corr(addr_str):
	addr_str = re_split(r"\(corresponding author\)", addr_str)[0]
	author_list = addr_str.split(";")
	author_list = [i.strip() for i in author_list]
	return author_list

def replace_fullname_scopus_general(addr_str, fullname_list):
	if "↔" in addr_str:
		parts_join_str = "↔"
	else:
		parts_join_str = ","
	if not parts_join_str in addr_str:
		coll_part = addr_str
	else:
		coll_part = parts_join_str.join(addr_str.split(parts_join_str)[1:])

	return f"{fullname_list[0]}↔{coll_part}"

def replace_fullname_scopus_corr(addr_str, fullname_list):
	coll_part = ";".join(addr_str.split(";")[1:])
	return f"{fullname_list[0]};{coll_part}"

def replace_fullname_wos_general(addr_str, fullname_list):
	# 地址中没有作者
	if not "]" in addr_str:
		return addr_str
	coll_part = addr_str.split("]")[1]
	fullname_str = ";".join(fullname_list)

	return f"{fullname_str}]{coll_part}"

def replace_fullname_wos_corr(addr_str, fullname_list):
	coll_part = re_split(r"\(corresponding author\)", addr_str)[1]
	fullname_str = ";".join(fullname_list)

	return f"{fullname_str}(corresponding author){coll_part}"

SPLIT_FUNC_DICT = {
	"SCOPUS一般地址": addr_split_scopus_general,
	"SCOPUS通讯地址": addr_split_scopus_corr,
	"WOS一般地址": addr_split_wos_general,
	"WOS通讯地址": addr_split_wos_corr,
}

JOIN_STR_DICT = {
	"SCOPUS一般地址": ";",
	"SCOPUS通讯地址": ";",
	"WOS一般地址": "; [",
	"WOS通讯地址": ";",
}

EXTRACT_FUNC_DICT = {
	"SCOPUS一般地址": extract_author_scopus_general,
	"SCOPUS通讯地址": extract_author_scopus_corr,
	"WOS一般地址": extract_author_wos_general,
	"WOS通讯地址": extract_author_wos_corr,
}

REPLACE_FULLNAME_FUNC_DICT = {
	"SCOPUS一般地址": replace_fullname_scopus_general,
	"SCOPUS通讯地址": replace_fullname_scopus_corr,
	"WOS一般地址": replace_fullname_wos_general,
	"WOS通讯地址": replace_fullname_wos_corr,
}

WEIGHT_ALLOCATOR_OF_ALGORITHM = [EqualWeightAllocator(), AllWeightAllocator(), ExprWeightAllocator("2*(n-k+1)/((n+1)*(n-2))")]

if __name__ == "__main__":
	print(WEIGHT_ALLOCATOR_OF_ALGORITHM[1].get_weights(3))